package com.asialjim.wechat.executor;

import com.asialjim.wechat.apiservice.AccessTokenService;
import com.asialjim.wechat.apiservice.Api;
import com.asialjim.wechat.apiservice.ApiRequest;
import com.asialjim.wechat.apiservice.HttpMethod;
import com.asialjim.wechat.apiservice.enumeration.WeChatApiReturnEnumeration;
import com.asialjim.wechat.apiservice.exception.UnSupportHttpMethodException;
import com.asialjim.wechat.apiservice.exception.UnSupportHttpResponseTypeException;
import com.asialjim.wechat.apiservice.exception.WeChatApiException;
import com.asialjim.wechat.apiservice.executor.ApiExecutor;
import com.asialjim.wechat.apiservice.executor.ApiExecutorBuilder;
import com.asialjim.wechat.core.api.BaseWeChatApi;
import com.asialjim.wechat.core.api.WeChatApiAccessTokenType;
import com.asialjim.wechat.core.api.WeChatApiRes;
import com.asialjim.wechat.core.executor.WeChatApiExecutor;
import com.asialjim.wechat.utils.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.http.HttpHost;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * <h1><em>ASIAL JIM JAVA DOC</em></h1><hr/>
 * <h2>CLASS DESCRIPTION <i>[ NAME: WeChatApiExecutor ]</i></h2><strong>
 * <p> we-chat api executor
 * </strong><p><p><em>Copyright &copy; Asial Jim Co., LTD</em><hr/>
 *
 * @author Asial Jim &nbsp;&nbsp; <span>Email: <a href="mailto:asialjim@hotmail.com">asialjim@hotmail.com</a> &nbsp;&nbsp; <a href="asialjim@qq.com">asialjim@qq.com</a></span>
 * @version 1.0.0
 * @since 2021/3/2   &nbsp;&nbsp; JDK 8
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WeChatApiExecutorImpl implements WeChatApiExecutor {
    private static final Logger log = LoggerFactory.getLogger(WeChatApiExecutorImpl.class);

    /**
     * <h3>FIELD DESCRIPTION</h3>
     * we-chat business api url domain
     */
    private String domain;

    /**
     * <h3>FIELD DESCRIPTION</h3>
     * temp file path when api function is download something
     */
    private String tempFilePath;

    private String applicationDomain;

    /**
     * <h3>FIELD DESCRIPTION</h3>
     * http proxy
     */
    private HttpHost httpProxy;

    /**
     * <h3>FIELD DESCRIPTION</h3>
     * we-chat api max retry times
     */
    @Builder.Default
    private Integer weChatApiMaxRetry = 3;

    /**
     * <h3>FIELD DESCRIPTION</h3>
     * access token service
     */
    private AccessTokenService accessTokenService;

    /**
     * <h3>FIELD DESCRIPTION</h3>
     * executor map
     */
    private Map<HttpMethod, ApiExecutor> executorMap;

    /**
     * <h3>METHOD DESCRIPTION <i>[ NAME: execute ]</i></h3>
     * execute we-chat api
     * <p> the executor will be create by default constructor method or {@link #setExecutorMap(Map)}
     * @param api {@link Api api}
     * @param urlParam {@link ApiRequest.RequestUrlParam url param}
     * @param bodyParam {@link ApiRequest.RequestBodyParam body param}
     * @param responseClass {@link Class<T> response class}
     * @return {@link T response}
     * @author Asial Jim &nbsp;&nbsp;<span>Email:asialjim@hotmail.com &nbsp;&nbsp; asialjim@qq.com</span>
     * @since 2021/3/2 11:45
     */
    @Override
    public <T> T execute(BaseWeChatApi api, ApiRequest.RequestUrlParam urlParam, ApiRequest.RequestBodyParam bodyParam, Class<T> responseClass){
        log.info("\r\n\tWE-CHAT API:[{}] execute, URL_PARAM:[{}], BODY_PARAM:[{}]",api.getName(),urlParam,Objects.nonNull(bodyParam) && Objects.isNull(bodyParam.file()) ? bodyParam : "FILE STREAM");
        if (Objects.isNull(executorMap)){
            executorMap = new HashMap<>();
            ApiExecutor executor = ApiExecutorBuilder.build(api, httpProxy, domain, tempFilePath);
            if (Objects.isNull(executor)) {
                throw UnSupportHttpMethodException.create(api.getHttpMethod().getName());
            }
            executorMap.put(api.getHttpMethod(),executor);
        }

        ApiExecutor executor = executorMap.get(api.getHttpMethod());
        if (Objects.isNull(executor)){
            executor = ApiExecutorBuilder.build(api,httpProxy,domain,tempFilePath);
            if (Objects.isNull(executor)) {
                throw UnSupportHttpMethodException.create(api.getHttpMethod().getName());
            }
            executorMap.put(api.getHttpMethod(), executor);
        }

        Map<String, Object> urlParamMap = JsonUtils.objectToMap(urlParam);
        if (Objects.isNull(urlParamMap)){
            urlParamMap = new HashMap<>();
        }

        if (api.accessTokenTypeEqual(WeChatApiAccessTokenType.COMMON)){
            urlParamMap.put("access_token",accessTokenService.get());
        }

        int count = 0;
        try {
            T result;
            do {
                count++;
                log.info("\r\n\tWE-CHAT API:[{}], COUNT:[{}]",api.getName(),count);
                result = executor.execute(api,urlParamMap, bodyParam,responseClass);
                if (Objects.isNull(result)){
                    continue;
                }

                unSupportHttpResponseTypeCheck(result);
                WeChatApiRes<?> res = (WeChatApiRes<?>)result;
                log.info("\r\n\tWE-CHAT API:[{}], \r\n\tEXECUTE TIMES:[{}], \r\n\tURL_PARAM:[{}], \r\n\tBODY_PARAM:[{}],\r\n\tRESULT:[{}]", api.getName(),count,urlParam,Objects.nonNull(bodyParam) && Objects.isNull(bodyParam.file()) ? bodyParam : "FILE STREAM",result);
                if (res.isSuccess()) {
                    return result;
                }
            } while (count < weChatApiMaxRetry);

            throw weChatApiResponseError(result);
        } catch (IOException e) {
            log.warn("\r\n\tWE-CHAT API:[{}] EXECUTE EXCEPTION:[{}],EXECUTE TIMES:[{}]",api.getName(),e.getMessage(),count,e);
            throw WeChatApiException.create(WeChatApiReturnEnumeration.CODE_1,e.getMessage());
        }
    }

    private <T> void unSupportHttpResponseTypeCheck(T result) {
        if (!(result instanceof WeChatApiRes)) { UnSupportHttpResponseTypeException.cast(result.getClass().getName()); }
    }

    private <T> WeChatApiException weChatApiResponseError(T result){
        if (Objects.isNull(result)){
            return WeChatApiException.create(WeChatApiReturnEnumeration.CODE_1,"we-chat api response is null");
        }

        WeChatApiRes<?> res = (WeChatApiRes<?>)result;
        return WeChatApiException.create(WeChatApiReturnEnumeration.codeOf(res.getErrcode()),res.getErrmsg());
    }
}